﻿using System.Diagnostics;
using DimensionsHelper.LanguageService.Protocol;

namespace DimensionsHelper.LanguageService.Syntax;

/// <summary>
///     在记录节点位置时，主要关注节点的字符索引和宽度，行号在判断行注释是否连续和获取#line指令的值时
///     有需求，对于列号基本没有需求，而且，由于制表符(\t)的宽度不固定，读取字符时获取的列号也不一定准确，
///     因此，放弃在扫描文本时保存列号。
/// </summary>
/// <param name="Position">文本字符位置，0开始</param>
/// <param name="Line">文本行号，1开始</param>
public record SyntaxPoint(int Position, int Line);


/// <summary>
///     脚本的文本区间，是左闭右开的区间，截取字符串时不包含结束位置的字符。
/// </summary>
[DebuggerDisplay("{ToDisplayString(),nq}")]
public readonly struct TextSpan : IEquatable<TextSpan>, IComparable<TextSpan>
{
    private TextSpan(DocumentUri uri, int start, int width)
    {
        Uri = uri;
        Start = start;
        Width = width;
    }


    /// <summary>
    ///     创建一个新的文本区间对象
    /// </summary>
    /// <param name="uri">区间所在文件URI</param>
    /// <param name="start">起始位置</param>
    /// <param name="length">区间宽度</param>
    /// <returns></returns>
    public static TextSpan Create(DocumentUri uri, int start, int length)
    {
        return new TextSpan(uri, start, length);
    }

    /// <summary>
    ///     创建一个起始为0宽度为1的文本区间
    /// </summary>
    /// <param name="uri">区间所在文件URI</param>
    public static TextSpan StartOfFile(DocumentUri uri)
    {
        return Create(uri, 0, 1);
    }


    /// <summary>
    ///     文件路径URI
    /// </summary>
    public DocumentUri Uri { get; }


    /// <summary>
    ///     文本区间所在的文件名
    /// </summary>
    private string FileName => DocumentUri.UriToFsPath(Uri, true);


    /// <summary>
    ///     获取文本区间的起始位置
    /// </summary>
    public int Start { get; }


    /// <summary>
    ///     获取文本区间的结束位置
    /// </summary>
    public int End => Start + Width;


    /// <summary>
    ///     获取文本区间的宽度
    /// </summary>
    public int Width { get; }


    public bool Equals(TextSpan other)
    {
        return FileName.Equals(other.FileName, StringComparison.OrdinalIgnoreCase)
               && Start == other.Start && Width == other.Width;
    }


    /// <summary>
    ///     如果当前节点长度是0，返回true。
    /// </summary>
    public bool IsMissing => Width == 0;


    /// <summary>
    ///     判断给定区间是否完全包含在此区间内。
    /// </summary>
    public bool Contains(TextSpan other)
    {
        return Start <= other.Start && End >= other.End;
    }


    /// <summary>
    ///     判断给定位置是否在此区间内。此区间是左闭右开区间。
    /// </summary>
    public bool Contains(int position)
    {
        return Start <= position && End > position;
    }


    public override bool Equals(object? obj)
    {
        if (obj is TextSpan range)
        {
            return Equals(range);
        }

        return false;
    }


    public override int GetHashCode()
    {
        return HashCode.Combine(Start, Width);
    }


    public int CompareTo(TextSpan other)
    {
        int res = Start - other.Start;

        if (res == 0)
        {
            res = Width - other.Width;
        }

        return res;
    }


    public static bool operator ==(TextSpan? left, TextSpan? right)
    {
        if (left is null)
        {
            return right is null;
        }

        return left.Equals(right);
    }


    public static bool operator !=(TextSpan? left, TextSpan? right)
    {
        if (left is null)
        {
            return right is not null;
        }

        return !left.Equals(right);
    }


    public static bool operator >(TextSpan left, TextSpan right)
    {
        return left.Start > right.Start
               || (left.Start == right.Start && left.Width > right.Width);
    }


    public static bool operator <(TextSpan left, TextSpan right)
    {
        return left.Start < right.Start
               || (left.Start == right.Start && left.Width < right.Width);
    }


    public static bool operator >=(TextSpan left, TextSpan right)
    {
        return left.Start > right.Start
               || (left.Start == right.Start && left.Width >= right.Width);
    }


    public static bool operator <=(TextSpan left, TextSpan right)
    {
        return left.Start < right.Start
               || (left.Start == right.Start && left.Width <= right.Width);
    }


    public string ToDisplayString()
    {
        return $"[{Start}..{End})";
    }


    public override string ToString()
    {
        return ToDisplayString();
    }
}